package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.PasswordConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.exception.AccountLockedException;
import com.sky.exception.AccountNotFoundException;
import com.sky.exception.PasswordErrorException;
import com.sky.mapper.EmployeeMapper;
import com.sky.result.PageResult;
import com.sky.service.EmployeeService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    /**
     * 员工登录
     *
     * @param employeeLoginDTO
     * @return
     */
    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();   //获得前端传进来用户名和密码，并取出来
        String password = employeeLoginDTO.getPassword();

        //1、根据用户名查询数据库中的数据  （调用mapper层查询数据库）
        Employee employee = employeeMapper.getByUsername(username);

        //2、处理各种异常情况（用户名不存在、密码不对、账号被锁定）
        if (employee == null) {
            //账号不存在    （AccountNotFoundException是我们自定义的异常类，代表账号不存在，这个异常类在sky-common下的exception包中）
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }//抛出异常后程序如何处理，因此我们需要一个地方统一捕获，在我们sky-server中提供一个handler，里面有个GlobalExceptionHandler全局异常处理器，在这个地方统一处理这些异常

        //密码比对  （这里比密码错误就抛出异常）
        //对前端传过来的明文密码进行md5加密处理  DigestUtils(spring...util）  md5DigestAsHex(byte[] bytes)    getBytes()
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }
        //判断账号是否锁定
        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定     status，0代表禁用（也就是锁定），1代表启用
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

        //3、返回实体对象（谁调用返回谁）
        return employee;
    }

    /**
     * 新增员工
     * @param employeeDTO
     */
    //这里注意，传进来的是dto，这是为了方便我们前端提交过来的数据，但最终是传给我们的持久层mapper的，所以我们建议还是使用我们的实体类进行新增操作，也就是说在这个地方需要进行一个对象的转换，将dto转成实体，这里实体对应的是employee实体
    public void save(EmployeeDTO employeeDTO) {
        System.out.println("当前线程的id：" + Thread.currentThread().getId());
        Employee employee = new Employee();

        //对象属性拷贝
        //因为employee实体是空的，所以我们可以通过employee.setName();等来设置一个一个属性,但是一个一个属性设置代码来写太繁琐，而且dto和实体类有些属性名一致，所以使用spring工具类进行拷贝
        //spring里边提供了一个BeanUtils工具类（包是org.springfra...beans)，copyProperties(a,b),从a拷贝到b,前提是属性名必须一致
        BeanUtils.copyProperties(employeeDTO,employee);

        //设置账号状态，默认正常状态 1表示正常 0表示锁定
        employee.setStatus(StatusConstant.ENABLE);

        //设置密码，默认密码123456   使用md5加密
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

        //设置当前记录的创建时间和修改时间  获取当前时间直接LocalDateTime.now()
        //employee.setCreateTime(LocalDateTime.now());
        //employee.setUpdateTime(LocalDateTime.now());
        //设置当前记录创建人id和修改人id     其实指的是我们当前登录用户的id，通过我们这些参数是获取不到的，我们先暂时写死，等后面再介绍一个新的技术再解决这个问题
        //employee.setCreateUser(BaseContext.getCurrentId());     //BaseContext.getCurrentId()，把线程的存储空间的当前empid提取出来
        //employee.setUpdateUser(BaseContext.getCurrentId());
//上面这两个是属于公共字段赋值，已经使用AOP方法自定义一个注解AutoFill和一个切面AutoFillAspect，并在mapper中是只用注解，所以这里可以直接不用了

        employeeMapper.insert(employee);
    }

    /**
     * 分页查询
     * @param employeePageQueryDTO
     * @return
     */
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        // select * from employee limit 0.10
        // 底层是基于数据库limit关键字查询，我们的dto已经把总记录数和每页记录数传进来，这样我们就可以动态地把这两个参数’0，10‘计算出来，然后给它拼到SQL里面去。但这需要我们自己去拼，mybatis框架提供一个插件帮我们拼，简化代码编写，要使用它就要再pom里面导入pagehelper进来
        //开始分页查询
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());  //getPage()是页码，getPageSize()每页记录数     //这个插件，它其实底层是基于我们mybatis的拦截器来实现，也就是它会把我们后边一条SQL进行一个动态的拼接

        Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);     //返回的结果是什么，因为使用了PageHelper插件，遵循PageHelper规则，所以返回的结果是固定的，叫Page，泛型是Employee实体

        //因为上面返回的是page，方法要返回pageQuery对象，所以要重新封装成pageQuery对象
        long total = page.getTotal();
        List<Employee> records = page.getResult();

        return new PageResult(total,records);   //因为PageResult有@AllArgsConstructor注解，可以直接new返回
    }

    /**
     * 启用禁用员工账号
     * @param status
     * @param id
     * @return
     */
    //启用禁用账号执行SQL，本质上是修改表里面的status字段，很显然根据id来修改
    public void startOrStop(Integer status, Long id) {
        //0 update employee set status = ? where id = ?
        //0 为了通用性，把update语句写成动态的，也就是说不只是修改status，根据传进来的status参数不同，可以修改多个字段

        //2.1 这是传统的创建实体类方式
        /*Employee employee = new Employee();
        employee.setStatus(status);
        employee.setId(id);*/

        //2.2 因为实体类Employee有注解@Builder，这注解叫构建器，所以我们可以直接使用构建器构建对象，并设置属性
        Employee employee = Employee.builder()
                .status(status)
                .id(id)
                .build();

        //1.我们可以直接调mapper的update方法做一个动态更新。update括号参数，动态更新直接传status和id这两个参数不合适，传对应的实体类比较合适，因此可以直接创建employee对象
        employeeMapper.update(employee);
    }

    /**
     * 根据id查询员工
     * @param id
     * @return
     */
    public Employee getById(Long id) {
        Employee employee = employeeMapper.getById(id);     //这里同controller，我们需要返回Employee对象
        //这里返回的实体类employee包含了密码或者加密密码，不想返回密码给前端，需要处理一下
        employee.setPassword("****");   //将返回的密码设置为****，这样返回给前端的，前端只能看到四个*的密码       //还有一种方法：直接在实体类的password上添加@JsonIgnore，这样这个实体类在给前端返回json数据时就不会携带password了
        return employee;
    }

    /**
     * 编辑员工信息
     * @param employeeDTO
     * @return
     */
    public void update(EmployeeDTO employeeDTO) {
        //employeeMapper.update();    //1.mapper里已经有update方法，这个方法在启用禁用时，当时做了一个动态的，也就是说所有的字段都能去修改，主要看xml这些属性是不是给我们传进来的，只要不为空，它就会把对应字段给修改掉
        //2.因为mapper中是Employee类型对象，而方法返回的是dto对象，所以需要转换，这里我们可以直接过对象的属性拷贝就可以了，步骤或解释参考“新增员工”
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);

        //下面这两个是属于公共字段赋值，已经使用AOP方法自定义一个注解AutoFill和一个切面AutoFillAspect，并在mapper中是只用注解，所以这里可以直接不用了
        //employee.setUpdateTime(LocalDateTime.now());
        //employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);
    }
}
